import asyncio
import aiohttp
import re
import chardet  # 自动检测网页编码

# 配置参数
NETWORK = "0.0.0.0/24"  # 目标网段
PORTS = [80, 443, 8080]  # 需要扫描的端口
TIMEOUT = 2  # 请求超时时间
SEMAPHORE = asyncio.Semaphore(50)  # 限制并发任务数量
MAX_RETRIES = 2  # 失败时的最大重试次数

async def fetch_title(session, url):
    """尝试获取网页标题，自动调整编码"""
    async with SEMAPHORE:
        for attempt in range(MAX_RETRIES):
            try:
                async with session.get(url, timeout=TIMEOUT) as response:
                    if response.status == 200:
                        raw_data = await response.read()  # 读取二进制数据

                        # 自动检测编码
                        detected_encoding = chardet.detect(raw_data)['encoding']
                        if not detected_encoding:
                            detected_encoding = 'utf-8'  # 默认尝试 utf-8

                        # 解析网页内容，遇到错误则回退
                        try:
                            text = raw_data.decode(detected_encoding, errors='ignore')
                        except UnicodeDecodeError:
                            try:
                                text = raw_data.decode('gb18030', errors='ignore')  # 尝试 GB2312/GBK
                            except UnicodeDecodeError:
                                text = raw_data.decode('ISO-8859-1', errors='ignore')  # 最后尝试 ISO 编码

                        # 提取<title>
                        title_match = re.search(r'<title>(.*?)</title>', text, re.IGNORECASE | re.DOTALL)
                        title = title_match.group(1).strip() if title_match else "No Title"
                        print(f"[+] {url} ({detected_encoding}) -> {title}")
                        return url, title
                    else:
                        # print(f"[-] {url} returned status {response.status}")
                        pass
            except aiohttp.ClientError as e:
                # print(f"[!] Error fetching {url}: {e}")
                pass
            except asyncio.TimeoutError:
                # print(f"[!] Timeout: {url}")
                pass
            except Exception as e:
                # print(f"[!] Unexpected error on {url}: {e}")
                pass
    return url, None

async def scan_ip(ip, session):
    """扫描单个 IP 地址的多个端口"""
    tasks = []
    for port in PORTS:
        url = f"http://{ip}:{port}" if port != 443 else f"https://{ip}"
        tasks.append(fetch_title(session, url))
    
    results = await asyncio.gather(*tasks)
    return [res for res in results if res[1]]  # 仅返回有标题的结果

async def main():
    """主程序，扫描整个网段"""
    base_ip = NETWORK.rsplit('.', 1)[0] + '.'
    ips = [f"{base_ip}{i}" for i in range(1, 255)]

    connector = aiohttp.TCPConnector(limit_per_host=10, ssl=False)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = [asyncio.create_task(scan_ip(ip, session)) for ip in ips]
        results = await asyncio.gather(*tasks)

    # 展示最终结果
    found_titles = [item for sublist in results for item in sublist]
    if found_titles:
        print("\n=== Web Services Found ===")
        for url, title in found_titles:
            print(f"{url} -> {title}")
    else:
        print("\n[!] No web services detected.")

if __name__ == "__main__":
    asyncio.run(main())
